// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/quic/quic_chromium_client_session.h"

#include <utility>

#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/network_activity_monitor.h"
#include "net/http/http_log_util.h"
#include "net/http/transport_security_state.h"
#include "net/quic/crypto/proof_verifier_chromium.h"
#include "net/quic/crypto/quic_server_info.h"
#include "net/quic/quic_chromium_connection_helper.h"
#include "net/quic/quic_client_promised_info.h"
#include "net/quic/quic_crypto_client_stream_factory.h"
#include "net/quic/quic_stream_factory.h"
#include "net/spdy/spdy_session.h"
#include "net/ssl/channel_id_service.h"
#include "net/ssl/ssl_connection_status_flags.h"
#include "net/ssl/ssl_info.h"
#include "net/ssl/token_binding.h"
#include "net/udp/datagram_client_socket.h"

namespace net {

namespace {

    // The length of time to wait for a 0-RTT handshake to complete
    // before allowing the requests to possibly proceed over TCP.
    const int k0RttHandshakeTimeoutMs = 300;

    // IPv6 packets have an additional 20 bytes of overhead than IPv4 packets.
    const size_t kAdditionalOverheadForIPv6 = 20;

    // Maximum number of Readers that are created for any session due to
    // connection migration. A new Reader is created every time this endpoint's
    // IP address changes.
    const size_t kMaxReadersPerQuicSession = 5;

    // Size of the MRU cache of Token Binding signatures. Since the material being
    // signed is constant and there aren't many keys being used to sign, a fairly
    // small number was chosen, somewhat arbitrarily, and to match
    // SSLClientSocketImpl.
    const size_t kTokenBindingSignatureMapSize = 10;

    // Histograms for tracking down the crashes from http://crbug.com/354669
    // Note: these values must be kept in sync with the corresponding values in:
    // tools/metrics/histograms/histograms.xml
    enum Location {
        DESTRUCTOR = 0,
        ADD_OBSERVER = 1,
        TRY_CREATE_STREAM = 2,
        CREATE_OUTGOING_RELIABLE_STREAM = 3,
        NOTIFY_FACTORY_OF_SESSION_CLOSED_LATER = 4,
        NOTIFY_FACTORY_OF_SESSION_CLOSED = 5,
        NUM_LOCATIONS = 6,
    };

    void RecordUnexpectedOpenStreams(Location location)
    {
        UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.UnexpectedOpenStreams", location,
            NUM_LOCATIONS);
    }

    void RecordUnexpectedObservers(Location location)
    {
        UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.UnexpectedObservers", location,
            NUM_LOCATIONS);
    }

    void RecordUnexpectedNotGoingAway(Location location)
    {
        UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.UnexpectedNotGoingAway", location,
            NUM_LOCATIONS);
    }

    // Histogram for recording the different reasons that a QUIC session is unable
    // to complete the handshake.
    enum HandshakeFailureReason {
        HANDSHAKE_FAILURE_UNKNOWN = 0,
        HANDSHAKE_FAILURE_BLACK_HOLE = 1,
        HANDSHAKE_FAILURE_PUBLIC_RESET = 2,
        NUM_HANDSHAKE_FAILURE_REASONS = 3,
    };

    void RecordHandshakeFailureReason(HandshakeFailureReason reason)
    {
        UMA_HISTOGRAM_ENUMERATION(
            "Net.QuicSession.ConnectionClose.HandshakeNotConfirmed.Reason", reason,
            NUM_HANDSHAKE_FAILURE_REASONS);
    }

    // Note: these values must be kept in sync with the corresponding values in:
    // tools/metrics/histograms/histograms.xml
    enum HandshakeState {
        STATE_STARTED = 0,
        STATE_ENCRYPTION_ESTABLISHED = 1,
        STATE_HANDSHAKE_CONFIRMED = 2,
        STATE_FAILED = 3,
        NUM_HANDSHAKE_STATES = 4
    };

    void RecordHandshakeState(HandshakeState state)
    {
        UMA_HISTOGRAM_ENUMERATION("Net.QuicHandshakeState", state,
            NUM_HANDSHAKE_STATES);
    }

    std::unique_ptr<base::Value> NetLogQuicClientSessionCallback(
        const QuicServerId* server_id,
        int cert_verify_flags,
        bool require_confirmation,
        NetLogCaptureMode /* capture_mode */)
    {
        std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
        dict->SetString("host", server_id->host());
        dict->SetInteger("port", server_id->port());
        dict->SetBoolean("privacy_mode",
            server_id->privacy_mode() == PRIVACY_MODE_ENABLED);
        dict->SetBoolean("require_confirmation", require_confirmation);
        dict->SetInteger("cert_verify_flags", cert_verify_flags);
        return std::move(dict);
    }

    std::unique_ptr<base::Value> NetLogQuicPushPromiseReceivedCallback(
        const SpdyHeaderBlock* headers,
        SpdyStreamId stream_id,
        SpdyStreamId promised_stream_id,
        NetLogCaptureMode capture_mode)
    {
        std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
        dict->Set("headers", ElideSpdyHeaderBlockForNetLog(*headers, capture_mode));
        dict->SetInteger("id", stream_id);
        dict->SetInteger("promised_stream_id", promised_stream_id);
        return std::move(dict);
    }

    class HpackEncoderDebugVisitor : public QuicHeadersStream::HpackDebugVisitor {
        void OnUseEntry(QuicTime::Delta elapsed) override
        {
            UMA_HISTOGRAM_TIMES(
                "Net.QuicHpackEncoder.IndexedEntryAge",
                base::TimeDelta::FromMicroseconds(elapsed.ToMicroseconds()));
        }
    };

    class HpackDecoderDebugVisitor : public QuicHeadersStream::HpackDebugVisitor {
        void OnUseEntry(QuicTime::Delta elapsed) override
        {
            UMA_HISTOGRAM_TIMES(
                "Net.QuicHpackDecoder.IndexedEntryAge",
                base::TimeDelta::FromMicroseconds(elapsed.ToMicroseconds()));
        }
    };

} // namespace

QuicChromiumClientSession::StreamRequest::StreamRequest()
    : stream_(nullptr)
{
}

QuicChromiumClientSession::StreamRequest::~StreamRequest()
{
    CancelRequest();
}

int QuicChromiumClientSession::StreamRequest::StartRequest(
    const base::WeakPtr<QuicChromiumClientSession>& session,
    QuicChromiumClientStream** stream,
    const CompletionCallback& callback)
{
    session_ = session;
    stream_ = stream;
    int rv = session_->TryCreateStream(this, stream_);
    if (rv == ERR_IO_PENDING) {
        callback_ = callback;
    }

    return rv;
}

void QuicChromiumClientSession::StreamRequest::CancelRequest()
{
    if (session_)
        session_->CancelRequest(this);
    session_.reset();
    callback_.Reset();
}

void QuicChromiumClientSession::StreamRequest::OnRequestCompleteSuccess(
    QuicChromiumClientStream* stream)
{
    session_.reset();
    *stream_ = stream;
    base::ResetAndReturn(&callback_).Run(OK);
}

void QuicChromiumClientSession::StreamRequest::OnRequestCompleteFailure(
    int rv)
{
    session_.reset();
    base::ResetAndReturn(&callback_).Run(rv);
}

QuicChromiumClientSession::QuicChromiumClientSession(
    QuicConnection* connection,
    std::unique_ptr<DatagramClientSocket> socket,
    QuicStreamFactory* stream_factory,
    QuicCryptoClientStreamFactory* crypto_client_stream_factory,
    QuicClock* clock,
    TransportSecurityState* transport_security_state,
    std::unique_ptr<QuicServerInfo> server_info,
    const QuicServerId& server_id,
    int yield_after_packets,
    QuicTime::Delta yield_after_duration,
    int cert_verify_flags,
    const QuicConfig& config,
    QuicCryptoClientConfig* crypto_config,
    const char* const connection_description,
    base::TimeTicks dns_resolution_end_time,
    QuicClientPushPromiseIndex* push_promise_index,
    base::TaskRunner* task_runner,
    std::unique_ptr<SocketPerformanceWatcher> socket_performance_watcher,
    NetLog* net_log)
    : QuicClientSessionBase(connection, push_promise_index, config)
    , server_id_(server_id)
    , require_confirmation_(false)
    , stream_factory_(stream_factory)
    , transport_security_state_(transport_security_state)
    , server_info_(std::move(server_info))
    , pkp_bypassed_(false)
    , num_total_streams_(0)
    , task_runner_(task_runner)
    , net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_QUIC_SESSION))
    , dns_resolution_end_time_(dns_resolution_end_time)
    , logger_(new QuicConnectionLogger(this,
          connection_description,
          std::move(socket_performance_watcher),
          net_log_))
    , going_away_(false)
    , port_migration_detected_(false)
    , disabled_reason_(QUIC_DISABLED_NOT)
    , token_binding_signatures_(kTokenBindingSignatureMapSize)
    , streams_pushed_count_(0)
    , streams_pushed_and_claimed_count_(0)
    , weak_factory_(this)
{
    sockets_.push_back(std::move(socket));
    packet_readers_.push_back(base::WrapUnique(new QuicChromiumPacketReader(
        sockets_.back().get(), clock, this, yield_after_packets,
        yield_after_duration, net_log_)));
    crypto_stream_.reset(
        crypto_client_stream_factory->CreateQuicCryptoClientStream(
            server_id, this, base::WrapUnique(new ProofVerifyContextChromium(cert_verify_flags, net_log_)),
            crypto_config));
    connection->set_debug_visitor(logger_.get());
    connection->set_creator_debug_delegate(logger_.get());
    net_log_.BeginEvent(NetLog::TYPE_QUIC_SESSION,
        base::Bind(NetLogQuicClientSessionCallback, &server_id,
            cert_verify_flags, require_confirmation_));
    IPEndPoint address;
    if (socket && socket->GetLocalAddress(&address) == OK && address.GetFamily() == ADDRESS_FAMILY_IPV6) {
        connection->SetMaxPacketLength(connection->max_packet_length() - kAdditionalOverheadForIPv6);
    }
}

QuicChromiumClientSession::~QuicChromiumClientSession()
{
    if (!dynamic_streams().empty())
        RecordUnexpectedOpenStreams(DESTRUCTOR);
    if (!observers_.empty())
        RecordUnexpectedObservers(DESTRUCTOR);
    if (!going_away_)
        RecordUnexpectedNotGoingAway(DESTRUCTOR);

    while (!dynamic_streams().empty() || !observers_.empty() || !stream_requests_.empty()) {
        // The session must be closed before it is destroyed.
        DCHECK(dynamic_streams().empty());
        CloseAllStreams(ERR_UNEXPECTED);
        DCHECK(observers_.empty());
        CloseAllObservers(ERR_UNEXPECTED);

        connection()->set_debug_visitor(nullptr);
        net_log_.EndEvent(NetLog::TYPE_QUIC_SESSION);

        while (!stream_requests_.empty()) {
            StreamRequest* request = stream_requests_.front();
            stream_requests_.pop_front();
            request->OnRequestCompleteFailure(ERR_ABORTED);
        }
    }

    if (connection()->connected()) {
        // Ensure that the connection is closed by the time the session is
        // destroyed.
        connection()->CloseConnection(QUIC_INTERNAL_ERROR, "session torn down",
            ConnectionCloseBehavior::SILENT_CLOSE);
    }

    if (IsEncryptionEstablished())
        RecordHandshakeState(STATE_ENCRYPTION_ESTABLISHED);
    if (IsCryptoHandshakeConfirmed())
        RecordHandshakeState(STATE_HANDSHAKE_CONFIRMED);
    else
        RecordHandshakeState(STATE_FAILED);

    UMA_HISTOGRAM_COUNTS("Net.QuicSession.NumTotalStreams", num_total_streams_);
    UMA_HISTOGRAM_COUNTS("Net.QuicNumSentClientHellos",
        crypto_stream_->num_sent_client_hellos());
    UMA_HISTOGRAM_COUNTS("Net.QuicSession.Pushed", streams_pushed_count_);
    UMA_HISTOGRAM_COUNTS("Net.QuicSession.PushedAndClaimed",
        streams_pushed_and_claimed_count_);
    if (!IsCryptoHandshakeConfirmed())
        return;

    // Sending one client_hello means we had zero handshake-round-trips.
    int round_trip_handshakes = crypto_stream_->num_sent_client_hellos() - 1;

    // Don't bother with these histogram during tests, which mock out
    // num_sent_client_hellos().
    if (round_trip_handshakes < 0 || !stream_factory_)
        return;

    bool port_selected = stream_factory_->enable_port_selection();
    SSLInfo ssl_info;
    // QUIC supports only secure urls.
    if (GetSSLInfo(&ssl_info) && ssl_info.cert.get()) {
        if (!port_selected) {
            UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.ConnectRandomPortForHTTPS",
                round_trip_handshakes, 0, 3, 4);
            if (require_confirmation_) {
                UMA_HISTOGRAM_CUSTOM_COUNTS(
                    "Net.QuicSession.ConnectRandomPortRequiringConfirmationForHTTPS",
                    round_trip_handshakes, 0, 3, 4);
            }
        }
    }
    const QuicConnectionStats stats = connection()->GetStats();
    if (server_info_ && stats.min_rtt_us > 0) {
        base::TimeTicks wait_for_data_start_time = server_info_->wait_for_data_start_time();
        base::TimeTicks wait_for_data_end_time = server_info_->wait_for_data_end_time();
        if (!wait_for_data_start_time.is_null() && !wait_for_data_end_time.is_null()) {
            base::TimeDelta wait_time = wait_for_data_end_time - wait_for_data_start_time;
            const base::HistogramBase::Sample kMaxWaitToRtt = 1000;
            base::HistogramBase::Sample wait_to_rtt = static_cast<base::HistogramBase::Sample>(
                100 * wait_time.InMicroseconds() / stats.min_rtt_us);
            UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicServerInfo.WaitForDataReadyToRtt",
                wait_to_rtt, 0, kMaxWaitToRtt, 50);
        }
    }

    // The MTU used by QUIC is limited to a fairly small set of predefined values
    // (initial values and MTU discovery values), but does not fare well when
    // bucketed.  Because of that, a sparse histogram is used here.
    UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.ClientSideMtu",
        connection()->max_packet_length());
    UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.ServerSideMtu",
        stats.max_received_packet_size);

    UMA_HISTOGRAM_COUNTS("Net.QuicSession.MtuProbesSent",
        connection()->mtu_probe_count());

    if (stats.max_sequence_reordering == 0)
        return;
    const base::HistogramBase::Sample kMaxReordering = 100;
    base::HistogramBase::Sample reordering = kMaxReordering;
    if (stats.min_rtt_us > 0) {
        reordering = static_cast<base::HistogramBase::Sample>(
            100 * stats.max_time_reordering_us / stats.min_rtt_us);
    }
    UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.MaxReorderingTime", reordering,
        0, kMaxReordering, 50);
    if (stats.min_rtt_us > 100 * 1000) {
        UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.MaxReorderingTimeLongRtt",
            reordering, 0, kMaxReordering, 50);
    }
    UMA_HISTOGRAM_COUNTS(
        "Net.QuicSession.MaxReordering",
        static_cast<base::HistogramBase::Sample>(stats.max_sequence_reordering));
}

void QuicChromiumClientSession::Initialize()
{
    QuicClientSessionBase::Initialize();
    headers_stream()->SetHpackEncoderDebugVisitor(
        base::WrapUnique(new HpackEncoderDebugVisitor()));
    headers_stream()->SetHpackDecoderDebugVisitor(
        base::WrapUnique(new HpackDecoderDebugVisitor()));
}

void QuicChromiumClientSession::OnHeadersHeadOfLineBlocking(
    QuicTime::Delta delta)
{
    UMA_HISTOGRAM_TIMES(
        "Net.QuicSession.HeadersHOLBlockedTime",
        base::TimeDelta::FromMicroseconds(delta.ToMicroseconds()));
}

void QuicChromiumClientSession::OnStreamFrame(const QuicStreamFrame& frame)
{
    // Record total number of stream frames.
    UMA_HISTOGRAM_COUNTS("Net.QuicNumStreamFramesInPacket", 1);

    // Record number of frames per stream in packet.
    UMA_HISTOGRAM_COUNTS("Net.QuicNumStreamFramesPerStreamInPacket", 1);

    return QuicSpdySession::OnStreamFrame(frame);
}

void QuicChromiumClientSession::AddObserver(Observer* observer)
{
    if (going_away_) {
        RecordUnexpectedObservers(ADD_OBSERVER);
        observer->OnSessionClosed(ERR_UNEXPECTED, port_migration_detected_);
        return;
    }

    DCHECK(!ContainsKey(observers_, observer));
    observers_.insert(observer);
}

void QuicChromiumClientSession::RemoveObserver(Observer* observer)
{
    DCHECK(ContainsKey(observers_, observer));
    observers_.erase(observer);
}

int QuicChromiumClientSession::TryCreateStream(
    StreamRequest* request,
    QuicChromiumClientStream** stream)
{
    if (!crypto_stream_->encryption_established()) {
        DLOG(DFATAL) << "Encryption not established.";
        return ERR_CONNECTION_CLOSED;
    }

    if (goaway_received()) {
        DVLOG(1) << "Going away.";
        return ERR_CONNECTION_CLOSED;
    }

    if (!connection()->connected()) {
        DVLOG(1) << "Already closed.";
        return ERR_CONNECTION_CLOSED;
    }

    if (going_away_) {
        RecordUnexpectedOpenStreams(TRY_CREATE_STREAM);
        return ERR_CONNECTION_CLOSED;
    }

    if (GetNumOpenOutgoingStreams() < max_open_outgoing_streams()) {
        *stream = CreateOutgoingReliableStreamImpl();
        return OK;
    }

    stream_requests_.push_back(request);
    return ERR_IO_PENDING;
}

void QuicChromiumClientSession::CancelRequest(StreamRequest* request)
{
    // Remove |request| from the queue while preserving the order of the
    // other elements.
    StreamRequestQueue::iterator it = std::find(stream_requests_.begin(), stream_requests_.end(), request);
    if (it != stream_requests_.end()) {
        it = stream_requests_.erase(it);
    }
}

bool QuicChromiumClientSession::ShouldCreateOutgoingDynamicStream()
{
    if (!crypto_stream_->encryption_established()) {
        DVLOG(1) << "Encryption not active so no outgoing stream created.";
        return false;
    }
    if (GetNumOpenOutgoingStreams() >= max_open_outgoing_streams()) {
        DVLOG(1) << "Failed to create a new outgoing stream. "
                 << "Already " << GetNumOpenOutgoingStreams() << " open.";
        return false;
    }
    if (goaway_received()) {
        DVLOG(1) << "Failed to create a new outgoing stream. "
                 << "Already received goaway.";
        return false;
    }
    if (going_away_) {
        RecordUnexpectedOpenStreams(CREATE_OUTGOING_RELIABLE_STREAM);
        return false;
    }
    return true;
}

QuicChromiumClientStream*
QuicChromiumClientSession::CreateOutgoingDynamicStream(SpdyPriority priority)
{
    if (!ShouldCreateOutgoingDynamicStream()) {
        return nullptr;
    }
    return CreateOutgoingReliableStreamImpl();
}

QuicChromiumClientStream*
QuicChromiumClientSession::CreateOutgoingReliableStreamImpl()
{
    DCHECK(connection()->connected());
    QuicChromiumClientStream* stream = new QuicChromiumClientStream(GetNextOutgoingStreamId(), this, net_log_);
    ActivateStream(stream);
    ++num_total_streams_;
    UMA_HISTOGRAM_COUNTS("Net.QuicSession.NumOpenStreams",
        GetNumOpenOutgoingStreams());
    // The previous histogram puts 100 in a bucket betweeen 86-113 which does
    // not shed light on if chrome ever things it has more than 100 streams open.
    UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.TooManyOpenStreams",
        GetNumOpenOutgoingStreams() > 100);
    return stream;
}

QuicCryptoClientStream* QuicChromiumClientSession::GetCryptoStream()
{
    return crypto_stream_.get();
}

// TODO(rtenneti): Add unittests for GetSSLInfo which exercise the various ways
// we learn about SSL info (sync vs async vs cached).
bool QuicChromiumClientSession::GetSSLInfo(SSLInfo* ssl_info) const
{
    ssl_info->Reset();
    if (!cert_verify_result_) {
        return false;
    }

    ssl_info->cert_status = cert_verify_result_->cert_status;
    ssl_info->cert = cert_verify_result_->verified_cert;

    // TODO(wtc): Define QUIC "cipher suites".
    // Report the TLS cipher suite that most closely resembles the crypto
    // parameters of the QUIC connection.
    QuicTag aead = crypto_stream_->crypto_negotiated_params().aead;
    uint16_t cipher_suite;
    int security_bits;
    switch (aead) {
    case kAESG:
        cipher_suite = 0xc02f; // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        security_bits = 128;
        break;
    case kCC20:
        cipher_suite = 0xcc13; // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
        security_bits = 256;
        break;
    default:
        NOTREACHED();
        return false;
    }
    int ssl_connection_status = 0;
    ssl_connection_status |= cipher_suite;
    ssl_connection_status |= (SSL_CONNECTION_VERSION_QUIC & SSL_CONNECTION_VERSION_MASK)
        << SSL_CONNECTION_VERSION_SHIFT;

    ssl_info->public_key_hashes = cert_verify_result_->public_key_hashes;
    ssl_info->is_issued_by_known_root = cert_verify_result_->is_issued_by_known_root;
    ssl_info->pkp_bypassed = pkp_bypassed_;

    ssl_info->connection_status = ssl_connection_status;
    ssl_info->client_cert_sent = false;
    ssl_info->channel_id_sent = crypto_stream_->WasChannelIDSent();
    ssl_info->security_bits = security_bits;
    ssl_info->handshake_type = SSLInfo::HANDSHAKE_FULL;
    ssl_info->pinning_failure_log = pinning_failure_log_;

    ssl_info->UpdateCertificateTransparencyInfo(*ct_verify_result_);

    if (crypto_stream_->crypto_negotiated_params().token_binding_key_param == kP256) {
        ssl_info->token_binding_negotiated = true;
        ssl_info->token_binding_key_param = TB_PARAM_ECDSAP256;
    }

    return true;
}

Error QuicChromiumClientSession::GetTokenBindingSignature(
    crypto::ECPrivateKey* key,
    std::vector<uint8_t>* out)
{
    // The same key will be used across multiple requests to sign the same value,
    // so the signature is cached.
    std::string raw_public_key;
    if (!key->ExportRawPublicKey(&raw_public_key))
        return ERR_FAILED;
    TokenBindingSignatureMap::iterator it = token_binding_signatures_.Get(raw_public_key);
    if (it != token_binding_signatures_.end()) {
        *out = it->second;
        return OK;
    }

    std::string key_material;
    if (!crypto_stream_->ExportTokenBindingKeyingMaterial(&key_material))
        return ERR_FAILED;
    if (!SignTokenBindingEkm(key_material, key, out))
        return ERR_FAILED;
    token_binding_signatures_.Put(raw_public_key, *out);
    return OK;
}

int QuicChromiumClientSession::CryptoConnect(
    bool require_confirmation,
    const CompletionCallback& callback)
{
    require_confirmation_ = require_confirmation;
    handshake_start_ = base::TimeTicks::Now();
    RecordHandshakeState(STATE_STARTED);
    DCHECK(flow_controller());
    crypto_stream_->CryptoConnect();

    if (IsCryptoHandshakeConfirmed())
        return OK;

    // Unless we require handshake confirmation, activate the session if
    // we have established initial encryption.
    if (!require_confirmation_ && IsEncryptionEstablished()) {
        // To mitigate the effects of hanging 0-RTT connections, set up a timer to
        // cancel any requests, if the handshake takes too long.
        task_runner_->PostDelayedTask(
            FROM_HERE, base::Bind(&QuicChromiumClientSession::OnConnectTimeout, weak_factory_.GetWeakPtr()),
            base::TimeDelta::FromMilliseconds(k0RttHandshakeTimeoutMs));
        return OK;
    }

    callback_ = callback;
    return ERR_IO_PENDING;
}

int QuicChromiumClientSession::ResumeCryptoConnect(
    const CompletionCallback& callback)
{
    if (IsCryptoHandshakeConfirmed())
        return OK;

    if (!connection()->connected())
        return ERR_QUIC_HANDSHAKE_FAILED;

    callback_ = callback;
    return ERR_IO_PENDING;
}

int QuicChromiumClientSession::GetNumSentClientHellos() const
{
    return crypto_stream_->num_sent_client_hellos();
}

bool QuicChromiumClientSession::CanPool(const std::string& hostname,
    PrivacyMode privacy_mode) const
{
    DCHECK(connection()->connected());
    if (privacy_mode != server_id_.privacy_mode()) {
        // Privacy mode must always match.
        return false;
    }
    SSLInfo ssl_info;
    if (!GetSSLInfo(&ssl_info) || !ssl_info.cert.get()) {
        NOTREACHED() << "QUIC should always have certificates.";
        return false;
    }

    return SpdySession::CanPool(transport_security_state_, ssl_info,
        server_id_.host(), hostname);
}

bool QuicChromiumClientSession::ShouldCreateIncomingDynamicStream(
    QuicStreamId id)
{
    if (!connection()->connected()) {
        LOG(DFATAL) << "ShouldCreateIncomingDynamicStream called when disconnected";
        return false;
    }
    if (goaway_received()) {
        DVLOG(1) << "Cannot create a new outgoing stream. "
                 << "Already received goaway.";
        return false;
    }
    if (going_away_) {
        return false;
    }
    if (id % 2 != 0) {
        LOG(WARNING) << "Received invalid push stream id " << id;
        connection()->CloseConnection(
            QUIC_INVALID_STREAM_ID, "Server created odd numbered stream",
            ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
        return false;
    }
    return true;
}

QuicChromiumClientStream*
QuicChromiumClientSession::CreateIncomingDynamicStream(QuicStreamId id)
{
    if (!ShouldCreateIncomingDynamicStream(id)) {
        return nullptr;
    }
    return CreateIncomingReliableStreamImpl(id);
}

QuicChromiumClientStream*
QuicChromiumClientSession::CreateIncomingReliableStreamImpl(QuicStreamId id)
{
    DCHECK(connection()->connected());
    QuicChromiumClientStream* stream = new QuicChromiumClientStream(id, this, net_log_);
    stream->CloseWriteSide();
    ActivateStream(stream);
    ++num_total_streams_;
    return stream;
}

void QuicChromiumClientSession::CloseStream(QuicStreamId stream_id)
{
    ReliableQuicStream* stream = GetOrCreateStream(stream_id);
    if (stream) {
        logger_->UpdateReceivedFrameCounts(stream_id, stream->num_frames_received(),
            stream->num_duplicate_frames_received());
    }
    QuicSpdySession::CloseStream(stream_id);
    OnClosedStream();
}

void QuicChromiumClientSession::SendRstStream(QuicStreamId id,
    QuicRstStreamErrorCode error,
    QuicStreamOffset bytes_written)
{
    QuicSpdySession::SendRstStream(id, error, bytes_written);
    OnClosedStream();
}

void QuicChromiumClientSession::OnClosedStream()
{
    if (GetNumOpenOutgoingStreams() < max_open_outgoing_streams() && !stream_requests_.empty() && crypto_stream_->encryption_established() && !goaway_received() && !going_away_ && connection()->connected()) {
        StreamRequest* request = stream_requests_.front();
        stream_requests_.pop_front();
        request->OnRequestCompleteSuccess(CreateOutgoingReliableStreamImpl());
    }

    if (GetNumOpenOutgoingStreams() == 0 && stream_factory_) {
        stream_factory_->OnIdleSession(this);
    }
}

void QuicChromiumClientSession::OnCryptoHandshakeEvent(
    CryptoHandshakeEvent event)
{
    if (stream_factory_ && event == HANDSHAKE_CONFIRMED && (stream_factory_->OnHandshakeConfirmed(this, logger_->ReceivedPacketLossRate()))) {
        return;
    }

    if (!callback_.is_null() && (!require_confirmation_ || event == HANDSHAKE_CONFIRMED || event == ENCRYPTION_REESTABLISHED)) {
        // TODO(rtenneti): Currently for all CryptoHandshakeEvent events, callback_
        // could be called because there are no error events in CryptoHandshakeEvent
        // enum. If error events are added to CryptoHandshakeEvent, then the
        // following code needs to changed.
        base::ResetAndReturn(&callback_).Run(OK);
    }
    if (event == HANDSHAKE_CONFIRMED) {
        UMA_HISTOGRAM_TIMES("Net.QuicSession.HandshakeConfirmedTime",
            base::TimeTicks::Now() - handshake_start_);
        if (server_info_) {
            // TODO(rtenneti): Should we delete this histogram?
            // Track how long it has taken to finish handshake once we start waiting
            // for reading of QUIC server information from disk cache. We could use
            // this data to compare total time taken if we were to cancel the disk
            // cache read vs waiting for the read to complete.
            base::TimeTicks wait_for_data_start_time = server_info_->wait_for_data_start_time();
            if (!wait_for_data_start_time.is_null()) {
                UMA_HISTOGRAM_TIMES(
                    "Net.QuicServerInfo.WaitForDataReady.HandshakeConfirmedTime",
                    base::TimeTicks::Now() - wait_for_data_start_time);
            }
        }
        // Track how long it has taken to finish handshake after we have finished
        // DNS host resolution.
        if (!dns_resolution_end_time_.is_null()) {
            UMA_HISTOGRAM_TIMES(
                "Net.QuicSession.HostResolution.HandshakeConfirmedTime",
                base::TimeTicks::Now() - dns_resolution_end_time_);
        }

        ObserverSet::iterator it = observers_.begin();
        while (it != observers_.end()) {
            Observer* observer = *it;
            ++it;
            observer->OnCryptoHandshakeConfirmed();
        }
        if (server_info_)
            server_info_->OnExternalCacheHit();
    }
    QuicSpdySession::OnCryptoHandshakeEvent(event);
}

void QuicChromiumClientSession::OnCryptoHandshakeMessageSent(
    const CryptoHandshakeMessage& message)
{
    logger_->OnCryptoHandshakeMessageSent(message);

    if (message.tag() == kREJ || message.tag() == kSREJ) {
        UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.RejectLength",
            message.GetSerialized().length(), 1000, 10000,
            50);
    }
}

void QuicChromiumClientSession::OnCryptoHandshakeMessageReceived(
    const CryptoHandshakeMessage& message)
{
    logger_->OnCryptoHandshakeMessageReceived(message);
}

void QuicChromiumClientSession::OnGoAway(const QuicGoAwayFrame& frame)
{
    QuicSession::OnGoAway(frame);
    NotifyFactoryOfSessionGoingAway();
    port_migration_detected_ = frame.error_code == QUIC_ERROR_MIGRATING_PORT;
}

void QuicChromiumClientSession::OnRstStream(const QuicRstStreamFrame& frame)
{
    QuicSession::OnRstStream(frame);
    OnClosedStream();
}

void QuicChromiumClientSession::OnConnectionClosed(
    QuicErrorCode error,
    const std::string& error_details,
    ConnectionCloseSource source)
{
    DCHECK(!connection()->connected());
    logger_->OnConnectionClosed(error, error_details, source);
    if (source == ConnectionCloseSource::FROM_PEER) {
        if (IsCryptoHandshakeConfirmed()) {
            UMA_HISTOGRAM_SPARSE_SLOWLY(
                "Net.QuicSession.ConnectionCloseErrorCodeServer.HandshakeConfirmed",
                error);
            base::HistogramBase* histogram = base::SparseHistogram::FactoryGet(
                "Net.QuicSession.StreamCloseErrorCodeServer.HandshakeConfirmed",
                base::HistogramBase::kUmaTargetedHistogramFlag);
            size_t num_streams = GetNumActiveStreams();
            if (num_streams > 0)
                histogram->AddCount(error, num_streams);
        }
        UMA_HISTOGRAM_SPARSE_SLOWLY(
            "Net.QuicSession.ConnectionCloseErrorCodeServer", error);
    } else {
        if (IsCryptoHandshakeConfirmed()) {
            UMA_HISTOGRAM_SPARSE_SLOWLY(
                "Net.QuicSession.ConnectionCloseErrorCodeClient.HandshakeConfirmed",
                error);
            base::HistogramBase* histogram = base::SparseHistogram::FactoryGet(
                "Net.QuicSession.StreamCloseErrorCodeClient.HandshakeConfirmed",
                base::HistogramBase::kUmaTargetedHistogramFlag);
            size_t num_streams = GetNumActiveStreams();
            if (num_streams > 0)
                histogram->AddCount(error, num_streams);
        }
        UMA_HISTOGRAM_SPARSE_SLOWLY(
            "Net.QuicSession.ConnectionCloseErrorCodeClient", error);
    }

    if (error == QUIC_NETWORK_IDLE_TIMEOUT) {
        UMA_HISTOGRAM_COUNTS(
            "Net.QuicSession.ConnectionClose.NumOpenStreams.TimedOut",
            GetNumOpenOutgoingStreams());
        if (IsCryptoHandshakeConfirmed()) {
            if (GetNumOpenOutgoingStreams() > 0) {
                disabled_reason_ = QUIC_DISABLED_TIMEOUT_WITH_OPEN_STREAMS;
                UMA_HISTOGRAM_BOOLEAN(
                    "Net.QuicSession.TimedOutWithOpenStreams.HasUnackedPackets",
                    connection()->sent_packet_manager().HasUnackedPackets());
                UMA_HISTOGRAM_COUNTS(
                    "Net.QuicSession.TimedOutWithOpenStreams.ConsecutiveRTOCount",
                    connection()->sent_packet_manager().GetConsecutiveRtoCount());
                UMA_HISTOGRAM_COUNTS(
                    "Net.QuicSession.TimedOutWithOpenStreams.ConsecutiveTLPCount",
                    connection()->sent_packet_manager().GetConsecutiveTlpCount());
            }
            if (connection()->sent_packet_manager().HasUnackedPackets()) {
                UMA_HISTOGRAM_TIMES(
                    "Net.QuicSession.LocallyTimedOutWithOpenStreams."
                    "TimeSinceLastReceived.UnackedPackets",
                    NetworkActivityMonitor::GetInstance()->GetTimeSinceLastReceived());
            } else {
                UMA_HISTOGRAM_TIMES(
                    "Net.QuicSession.LocallyTimedOutWithOpenStreams."
                    "TimeSinceLastReceived.NoUnackedPackets",
                    NetworkActivityMonitor::GetInstance()->GetTimeSinceLastReceived());
            }

        } else {
            UMA_HISTOGRAM_COUNTS(
                "Net.QuicSession.ConnectionClose.NumOpenStreams.HandshakeTimedOut",
                GetNumOpenOutgoingStreams());
            UMA_HISTOGRAM_COUNTS(
                "Net.QuicSession.ConnectionClose.NumTotalStreams.HandshakeTimedOut",
                num_total_streams_);
        }
    }

    if (!IsCryptoHandshakeConfirmed()) {
        if (error == QUIC_PUBLIC_RESET) {
            RecordHandshakeFailureReason(HANDSHAKE_FAILURE_PUBLIC_RESET);
        } else if (connection()->GetStats().packets_received == 0) {
            RecordHandshakeFailureReason(HANDSHAKE_FAILURE_BLACK_HOLE);
            UMA_HISTOGRAM_SPARSE_SLOWLY(
                "Net.QuicSession.ConnectionClose.HandshakeFailureBlackHole.QuicError",
                error);
        } else {
            RecordHandshakeFailureReason(HANDSHAKE_FAILURE_UNKNOWN);
            UMA_HISTOGRAM_SPARSE_SLOWLY(
                "Net.QuicSession.ConnectionClose.HandshakeFailureUnknown.QuicError",
                error);
        }
    } else if (error == QUIC_PUBLIC_RESET) {
        disabled_reason_ = QUIC_DISABLED_PUBLIC_RESET_POST_HANDSHAKE;
    }

    UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.QuicVersion",
        connection()->version());
    NotifyFactoryOfSessionGoingAway();
    QuicSession::OnConnectionClosed(error, error_details, source);

    if (!callback_.is_null()) {
        base::ResetAndReturn(&callback_).Run(ERR_QUIC_PROTOCOL_ERROR);
    }

    for (auto& socket : sockets_) {
        socket->Close();
    }
    DCHECK(dynamic_streams().empty());
    CloseAllStreams(ERR_UNEXPECTED);
    CloseAllObservers(ERR_UNEXPECTED);
    NotifyFactoryOfSessionClosedLater();
}

void QuicChromiumClientSession::OnSuccessfulVersionNegotiation(
    const QuicVersion& version)
{
    logger_->OnSuccessfulVersionNegotiation(version);
    QuicSpdySession::OnSuccessfulVersionNegotiation(version);
}

void QuicChromiumClientSession::OnPathDegrading()
{
    if (stream_factory_) {
        stream_factory_->MaybeMigrateSessionEarly(this);
    }
}

bool QuicChromiumClientSession::HasOpenDynamicStreams() const
{
    return QuicSession::HasOpenDynamicStreams() || GetNumDrainingOutgoingStreams() > 0;
}

void QuicChromiumClientSession::OnProofValid(
    const QuicCryptoClientConfig::CachedState& cached)
{
    DCHECK(cached.proof_valid());

    if (!server_info_) {
        return;
    }

    QuicServerInfo::State* state = server_info_->mutable_state();

    state->server_config = cached.server_config();
    state->source_address_token = cached.source_address_token();
    state->cert_sct = cached.cert_sct();
    state->chlo_hash = cached.chlo_hash();
    state->server_config_sig = cached.signature();
    state->certs = cached.certs();

    server_info_->Persist();
}

void QuicChromiumClientSession::OnProofVerifyDetailsAvailable(
    const ProofVerifyDetails& verify_details)
{
    const ProofVerifyDetailsChromium* verify_details_chromium = reinterpret_cast<const ProofVerifyDetailsChromium*>(&verify_details);
    cert_verify_result_.reset(
        new CertVerifyResult(verify_details_chromium->cert_verify_result));
    pinning_failure_log_ = verify_details_chromium->pinning_failure_log;
    std::unique_ptr<ct::CTVerifyResult> ct_verify_result_copy(
        new ct::CTVerifyResult(verify_details_chromium->ct_verify_result));
    ct_verify_result_ = std::move(ct_verify_result_copy);
    logger_->OnCertificateVerified(*cert_verify_result_);
    pkp_bypassed_ = verify_details_chromium->pkp_bypassed;
}

void QuicChromiumClientSession::StartReading()
{
    for (auto& packet_reader : packet_readers_) {
        packet_reader->StartReading();
    }
}

void QuicChromiumClientSession::CloseSessionOnError(int error,
    QuicErrorCode quic_error)
{
    RecordAndCloseSessionOnError(error, quic_error);
    NotifyFactoryOfSessionClosed();
}

void QuicChromiumClientSession::CloseSessionOnErrorAndNotifyFactoryLater(
    int error,
    QuicErrorCode quic_error)
{
    RecordAndCloseSessionOnError(error, quic_error);
    NotifyFactoryOfSessionClosedLater();
}

void QuicChromiumClientSession::RecordAndCloseSessionOnError(
    int error,
    QuicErrorCode quic_error)
{
    UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.CloseSessionOnError", -error);
    CloseSessionOnErrorInner(error, quic_error);
}

void QuicChromiumClientSession::CloseSessionOnErrorInner(
    int net_error,
    QuicErrorCode quic_error)
{
    if (!callback_.is_null()) {
        base::ResetAndReturn(&callback_).Run(net_error);
    }
    CloseAllStreams(net_error);
    CloseAllObservers(net_error);
    net_log_.AddEvent(NetLog::TYPE_QUIC_SESSION_CLOSE_ON_ERROR,
        NetLog::IntCallback("net_error", net_error));

    if (connection()->connected())
        connection()->CloseConnection(quic_error, "net error",
            ConnectionCloseBehavior::SILENT_CLOSE);
    DCHECK(!connection()->connected());
}

void QuicChromiumClientSession::CloseAllStreams(int net_error)
{
    while (!dynamic_streams().empty()) {
        ReliableQuicStream* stream = dynamic_streams().begin()->second;
        QuicStreamId id = stream->id();
        static_cast<QuicChromiumClientStream*>(stream)->OnError(net_error);
        CloseStream(id);
    }
}

void QuicChromiumClientSession::CloseAllObservers(int net_error)
{
    while (!observers_.empty()) {
        Observer* observer = *observers_.begin();
        observers_.erase(observer);
        observer->OnSessionClosed(net_error, port_migration_detected_);
    }
}

std::unique_ptr<base::Value> QuicChromiumClientSession::GetInfoAsValue(
    const std::set<HostPortPair>& aliases)
{
    std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
    dict->SetString("version", QuicVersionToString(connection()->version()));
    dict->SetInteger("open_streams", GetNumOpenOutgoingStreams());
    std::unique_ptr<base::ListValue> stream_list(new base::ListValue());
    for (StreamMap::const_iterator it = dynamic_streams().begin();
         it != dynamic_streams().end(); ++it) {
        stream_list->AppendString(base::UintToString(it->second->id()));
    }
    dict->Set("active_streams", std::move(stream_list));

    dict->SetInteger("total_streams", num_total_streams_);
    dict->SetString("peer_address", peer_address().ToString());
    dict->SetString("connection_id", base::Uint64ToString(connection_id()));
    dict->SetBoolean("connected", connection()->connected());
    const QuicConnectionStats& stats = connection()->GetStats();
    dict->SetInteger("packets_sent", stats.packets_sent);
    dict->SetInteger("packets_received", stats.packets_received);
    dict->SetInteger("packets_lost", stats.packets_lost);
    SSLInfo ssl_info;
    dict->SetBoolean("secure", GetSSLInfo(&ssl_info) && ssl_info.cert.get());

    std::unique_ptr<base::ListValue> alias_list(new base::ListValue());
    for (std::set<HostPortPair>::const_iterator it = aliases.begin();
         it != aliases.end(); it++) {
        alias_list->AppendString(it->ToString());
    }
    dict->Set("aliases", std::move(alias_list));

    return std::move(dict);
}

base::WeakPtr<QuicChromiumClientSession>
QuicChromiumClientSession::GetWeakPtr()
{
    return weak_factory_.GetWeakPtr();
}

void QuicChromiumClientSession::OnReadError(
    int result,
    const DatagramClientSocket* socket)
{
    DCHECK(socket != nullptr);
    if (socket != GetDefaultSocket()) {
        // Ignore read errors from old sockets that are no longer active.
        // TODO(jri): Maybe clean up old sockets on error.
        return;
    }
    DVLOG(1) << "Closing session on read error: " << result;
    UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.ReadError", -result);
    NotifyFactoryOfSessionGoingAway();
    CloseSessionOnErrorInner(result, QUIC_PACKET_READ_ERROR);
    NotifyFactoryOfSessionClosedLater();
}

bool QuicChromiumClientSession::OnPacket(const QuicReceivedPacket& packet,
    IPEndPoint local_address,
    IPEndPoint peer_address)
{
    ProcessUdpPacket(local_address, peer_address, packet);
    if (!connection()->connected()) {
        NotifyFactoryOfSessionClosedLater();
        return false;
    }
    return true;
}

void QuicChromiumClientSession::NotifyFactoryOfSessionGoingAway()
{
    going_away_ = true;
    if (stream_factory_)
        stream_factory_->OnSessionGoingAway(this);
}

void QuicChromiumClientSession::NotifyFactoryOfSessionClosedLater()
{
    if (!dynamic_streams().empty())
        RecordUnexpectedOpenStreams(NOTIFY_FACTORY_OF_SESSION_CLOSED_LATER);

    if (!going_away_)
        RecordUnexpectedNotGoingAway(NOTIFY_FACTORY_OF_SESSION_CLOSED_LATER);

    going_away_ = true;
    DCHECK_EQ(0u, GetNumActiveStreams());
    DCHECK(!connection()->connected());
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE,
        base::Bind(&QuicChromiumClientSession::NotifyFactoryOfSessionClosed,
            weak_factory_.GetWeakPtr()));
}

void QuicChromiumClientSession::NotifyFactoryOfSessionClosed()
{
    if (!dynamic_streams().empty())
        RecordUnexpectedOpenStreams(NOTIFY_FACTORY_OF_SESSION_CLOSED);

    if (!going_away_)
        RecordUnexpectedNotGoingAway(NOTIFY_FACTORY_OF_SESSION_CLOSED);

    going_away_ = true;
    DCHECK_EQ(0u, GetNumActiveStreams());
    // Will delete |this|.
    if (stream_factory_)
        stream_factory_->OnSessionClosed(this);
}

void QuicChromiumClientSession::OnConnectTimeout()
{
    DCHECK(callback_.is_null());

    if (IsCryptoHandshakeConfirmed())
        return;

    // TODO(rch): re-enable this code once beta is cut.
    //  if (stream_factory_)
    //    stream_factory_->OnSessionConnectTimeout(this);
    //  CloseAllStreams(ERR_QUIC_HANDSHAKE_FAILED);
    //  DCHECK_EQ(0u, GetNumOpenOutgoingStreams());
}

bool QuicChromiumClientSession::MigrateToSocket(
    std::unique_ptr<DatagramClientSocket> socket,
    std::unique_ptr<QuicChromiumPacketReader> reader,
    std::unique_ptr<QuicPacketWriter> writer)
{
    DCHECK_EQ(sockets_.size(), packet_readers_.size());
    if (sockets_.size() >= kMaxReadersPerQuicSession) {
        return false;
    }
    // TODO(jri): Make SetQuicPacketWriter take a scoped_ptr.
    connection()->SetQuicPacketWriter(writer.release(), /*owns_writer=*/true);
    packet_readers_.push_back(std::move(reader));
    sockets_.push_back(std::move(socket));
    StartReading();
    connection()->SendPing();
    return true;
}

void QuicChromiumClientSession::PopulateNetErrorDetails(
    NetErrorDetails* details)
{
    details->quic_port_migration_detected = port_migration_detected_;
}

const DatagramClientSocket* QuicChromiumClientSession::GetDefaultSocket()
    const
{
    DCHECK(sockets_.back().get() != nullptr);
    // The most recently added socket is the currently active one.
    return sockets_.back().get();
}

bool QuicChromiumClientSession::IsAuthorized(const std::string& hostname)
{
    bool result = CanPool(hostname, server_id_.privacy_mode());
    if (result)
        streams_pushed_count_++;
    return result;
}

bool QuicChromiumClientSession::HasNonMigratableStreams() const
{
    for (const auto& stream : dynamic_streams()) {
        if (!static_cast<QuicChromiumClientStream*>(stream.second)->can_migrate())
            return true;
    }
    return false;
}

void QuicChromiumClientSession::HandlePromised(QuicStreamId id,
    QuicStreamId promised_id,
    const SpdyHeaderBlock& headers)
{
    QuicClientSessionBase::HandlePromised(id, promised_id, headers);
    net_log_.AddEvent(NetLog::TYPE_QUIC_SESSION_PUSH_PROMISE_RECEIVED,
        base::Bind(&NetLogQuicPushPromiseReceivedCallback, &headers,
            id, promised_id));
}

void QuicChromiumClientSession::DeletePromised(
    QuicClientPromisedInfo* promised)
{
    if (IsOpenStream(promised->id()))
        streams_pushed_and_claimed_count_++;
    QuicClientSessionBase::DeletePromised(promised);
}

} // namespace net
